﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;
using System.Diagnostics.Contracts;
using System.IO;
namespace MDS
{
    public class DBFHelper
    {
        static System.Text.Encoding _defaultEncoding = System.Text.Encoding.Default;

        private static Dictionary<Type, DBFAttribute> _dbfAttrDic = new Dictionary<Type, DBFAttribute>();
        private static Dictionary<FieldInfo, DBFRecordFieldAttribute> _dbfRecordFieldAttrDic = new Dictionary<FieldInfo, DBFRecordFieldAttribute>();
        private static Dictionary<FieldInfo, Delegate> _setRecordFieldDelegateDic = new Dictionary<FieldInfo, Delegate>();


        public static List<T> ReadRecords<T>(string dbfFileFullPath) where T :struct 
        {
            FileStream dbfileStream = new FileStream(dbfFileFullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            byte[] fileBuffer = new byte[dbfileStream.Length];
            dbfileStream.Read(fileBuffer, 0, (int)dbfileStream.Length);
            dbfileStream.Close();
            return ReadRecords<T>(fileBuffer);
        }
        
        public static List<T> ReadRecords<T>(byte[] buffer) where T : struct
        {
            DBFAttribute dbfAttribute = getDBFAttribute(typeof(T));
            if (dbfAttribute == null)
            {
                return null;
            }
            else
            {
                List<T> rtn = new List<T>();
                if (buffer.Length > dbfAttribute.HeaderLength)
                {
                    int offset = dbfAttribute.HeaderLength;
                    while (offset < buffer.Length)
                    {
                        T record = readRecord<T>(buffer, offset);
                        rtn.Add(record);
                        offset += dbfAttribute.RecordLength;
                    }
                }
                return rtn;
            }
        }

        private static T readRecord<T>(byte[] buffer, int recordOffset) where T:struct
        {
            T rtn = new T();
            var fieldInfos = typeof(T).GetFields();
                DBFRecordFieldAttribute dbfRecordFieldAttr ;
                Delegate setRecordFieldDelegate ;
            foreach (var fieldInfo in fieldInfos)
            {
                if((dbfRecordFieldAttr = getDbfRecordFieldAttr(fieldInfo))!=null
                    && (setRecordFieldDelegate = getSetFieldDelegate<T>(fieldInfo))!=null)
                {

                    if (fieldInfo.FieldType == typeof(string))
                    {
                        string value = ReadFieldString(buffer, recordOffset, dbfRecordFieldAttr);
                        var dlg = setRecordFieldDelegate as ByRefAction<T, string>;
                        if (dlg != null)
                            dlg(ref rtn, value);
                    }
                    else if (fieldInfo.FieldType == typeof(int))
                    {
                        int value = ReadIntField(buffer, recordOffset, dbfRecordFieldAttr);
                        var dlg = setRecordFieldDelegate as ByRefAction<T, int>;
                        if (dlg != null)
                            dlg(ref rtn, value);
                    }
                    else if (fieldInfo.FieldType == typeof(long))
                    {
                        long value = ReadInt64Field(buffer, recordOffset, dbfRecordFieldAttr);
                        var dlg = setRecordFieldDelegate as ByRefAction<T, long>;
                        if (dlg != null)
                            dlg(ref rtn, value);
                    }
                    else if (fieldInfo.FieldType == typeof(double))
                    {
                        double value = ReadDoubleField(buffer, recordOffset, dbfRecordFieldAttr);
                        var dlg = setRecordFieldDelegate as ByRefAction<T, double>;
                        if (dlg != null)
                            dlg(ref rtn, value);
                    }

                }
       
            }
            return rtn;
        }

        private static Delegate getSetFieldDelegate<T>(FieldInfo fieldInfo)
        {
            if (_setRecordFieldDelegateDic.ContainsKey(fieldInfo))
                return _setRecordFieldDelegateDic[fieldInfo];
            else
            {
                Delegate dlg=null;
                if (fieldInfo.FieldType == typeof(string))
                {
                    dlg = getSetFieldDelegate<T, string>(fieldInfo);
                }
                else if (fieldInfo.FieldType == typeof(int))
                {
                    dlg = getSetFieldDelegate<T, int>(fieldInfo);
                }
                else if (fieldInfo.FieldType == typeof(long))
                {
                    dlg = getSetFieldDelegate<T, long>(fieldInfo);
                }
                else if (fieldInfo.FieldType == typeof(double))
                {
                    dlg = getSetFieldDelegate<T, double>(fieldInfo);
                }
                _setRecordFieldDelegateDic.Add(fieldInfo, dlg);
                return dlg;
            }
        }

        private static DBFRecordFieldAttribute getDbfRecordFieldAttr(FieldInfo fieldInfo)
        {
            Contract.Requires(fieldInfo != null);
            if (_dbfRecordFieldAttrDic.ContainsKey(fieldInfo))
                return _dbfRecordFieldAttrDic[fieldInfo];
            else
            {
                var attr = fieldInfo.GetCustomAttributes(typeof(DBFRecordFieldAttribute), false).FirstOrDefault() as DBFRecordFieldAttribute;
                _dbfRecordFieldAttrDic.Add(fieldInfo, attr);
                return attr;
            }
        }


        private static DBFAttribute getDBFAttribute(Type type)
        {
            Contract.Requires(type != null);

            if (_dbfAttrDic.ContainsKey(type))
                return _dbfAttrDic[type];
            else
            {
                var attr = type.GetCustomAttributes(typeof(DBFAttribute), false).FirstOrDefault() as DBFAttribute;
                _dbfAttrDic[type]= attr;
                return attr;
            }
           
        }

        private static ByRefAction<TSource, TValue> getSetFieldDelegate<TSource, TValue>(FieldInfo fieldInfo)
        {
            Contract.Requires(fieldInfo != null);
            Type fieldDeclaringType = fieldInfo.DeclaringType;

            ParameterExpression sourceParameter = Expression.Parameter(typeof(TSource).MakeByRefType(), "source");
            ParameterExpression valueParameter = Expression.Parameter(typeof(TValue), "value");

            Expression sourceExpression = Expression.Field(sourceParameter, fieldInfo);
            Expression assignExpression = Expression.Assign(sourceExpression, valueParameter);



            LambdaExpression lambda = Expression.Lambda(typeof(ByRefAction<TSource, TValue>),
                                                        assignExpression, sourceParameter, valueParameter);

            ByRefAction<TSource, TValue> result = (ByRefAction<TSource, TValue>)lambda.Compile();
            return result;
        }

        public static string ReadFieldString(byte[] buffer, int recordOffset, DBFRecordFieldAttribute field)
        {
            try
            {
                string str = _defaultEncoding.GetString(buffer, recordOffset + field.Offset, field.Length);
                if (!string.IsNullOrEmpty(str))
                {
                    str.Replace(" ", "");
                }
                return str;
            }
            catch (Exception ex)
            {
                return null;
            }
        }
        public static double ReadDoubleField(byte[] buffer, int recordOffset, DBFRecordFieldAttribute field)
        {
            string str = ReadFieldString(buffer, recordOffset, field);
            double rtn;
            double.TryParse(str, out rtn);
            return rtn;
        }
        public static int ReadIntField(byte[] buffer, int recordOffset, DBFRecordFieldAttribute field)
        {
            string str = ReadFieldString(buffer, recordOffset, field);
            int rtn;
            int.TryParse(str, out rtn);
            return rtn;
        }
        public static long ReadInt64Field(byte[] buffer, int recordOffset, DBFRecordFieldAttribute field)
        {
            string str = ReadFieldString(buffer, recordOffset, field);
            long rtn;
            long.TryParse(str, out rtn);
            return rtn;
        
        }






        public delegate void ByRefAction<T, TValue>(ref T obj, TValue value);

    }
}
